﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GoodStuff
{
    public static class DateTimeExtensions
    {
        public static DateTime RoundTo(this DateTime date, DateTimeRoundOption roundOption)
        {
            switch (roundOption)
            {
                case DateTimeRoundOption.Year:
                    return new DateTime(date.Year, 1, 1); 
                case DateTimeRoundOption.Month:
                    return new DateTime(date.Year, date.Month, 1);
                case DateTimeRoundOption.Day:
                    return new DateTime(date.Year, date.Month, date.Day);
                case DateTimeRoundOption.Hour:
                    return new DateTime(date.Year, date.Month, date.Day, date.Hour, 0, 0);
                case DateTimeRoundOption.Minute:
                    return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, 0);
                case DateTimeRoundOption.Second:
                    return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second);
                default:
                    throw new NotImplementedException();
            }
        }

        public static DateTime RoundTo(this DateTime date, DateTimeRoundOption roundOption, int interval)
        {
            switch (roundOption)
            {
                case DateTimeRoundOption.Year:
                    return new DateTime(date.Year.RoundTo(interval), 1, 1);
                case DateTimeRoundOption.Month:
                    return new DateTime(date.Year, date.Month.RoundTo(interval), 1);
                case DateTimeRoundOption.Day:
                    return new DateTime(date.Year, date.Month, date.Day.RoundTo(interval));
                case DateTimeRoundOption.Hour:
                    return new DateTime(date.Year, date.Month, date.Day, date.Hour.RoundTo(interval), 0, 0);
                case DateTimeRoundOption.Minute:
                    return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute.RoundTo(interval), 0);
                case DateTimeRoundOption.Second:
                    return new DateTime(date.Year, date.Month, date.Day, date.Hour, date.Minute, date.Second.RoundTo(interval));
                default:
                    throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Returns a number rounded to its closest interval, e.g. 17 RoundTo 4 becomes 16. 
        /// </summary>
        /// <param name="input"></param>
        /// <param name="interval"></param>
        private static int RoundTo(this int input, int interval)
        {
            int result;
            Math.DivRem(input, interval, out result);
            return input - result;
        }

        /// <summary>
        /// Assuming that we never work on a saturday or a sunday, compute the next number of working days.
        /// </summary>
        /// <param name="day">The day to start counting from, e.g. DateTime.Now</param>
        /// <param name="amount">The amount of dates to return</param>
        /// <param name="span">The number of working days to skip.</param>
        /// <param name="calendar">Supports custom implementations for obtaining holidays.</param>
        /// <returns></returns>
        public static DateTime NextWorkingDay(this DateTime day, int amount, int span, IWorkingdayProvider calendar)
        {
            List<DateTime> voorkeuren = new List<DateTime>();
            day = day.Date;
            while (span > 0)
            {
                if (IsWorkingDay(day, calendar))
                {
                    span--;
                }
                day = day.AddDays(1);
            }

            while (voorkeuren.Count < amount)
            {
                if (IsWorkingDay(day, calendar))
                {
                    voorkeuren.Add(day);
                }
                day = day.AddDays(1);
            }

            return day;
        }

        private static bool IsWorkingDay(DateTime day, IWorkingdayProvider calendar)
        {
            if(day.DayOfWeek == DayOfWeek.Saturday || day.DayOfWeek == DayOfWeek.Sunday)
                return false;

            if(calendar != null && !calendar.IsWorkingDay(day))
                return false;

            return true;
        }
    }

    public interface IWorkingdayProvider
    {
        bool IsWorkingDay(DateTime day);
    }

    public enum DateTimeRoundOption
    {
        Year,
        Month,
        Day,
        Hour,
        Minute,
        Second
    }
}
